Add support for saving multiple metadata styles in the GUI
Unwind credit color comprehension Convert save style from a string setting to a list Use lordwelch version of Checkable combobox Improve readbility, fix label alignment in taggerwindow.ui, better report removal of tags and clearer number meanings. Unwind list comprehension for easier readability
This commit is contained in:
parent
bc335f1686
commit
23021ba632
@ -39,7 +39,7 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
|
||||
self,
|
||||
parent: QtWidgets.QWidget,
|
||||
match_set_list: list[Result],
|
||||
style: str,
|
||||
styles: list[str],
|
||||
fetch_func: Callable[[IssueResult], GenericMetadata],
|
||||
config: ct_ns,
|
||||
talker: ComicTalker,
|
||||
@ -81,7 +81,7 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
|
||||
self.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Ok).setText("Accept and Write Tags")
|
||||
|
||||
self.match_set_list = match_set_list
|
||||
self._style = style
|
||||
self._styles = styles
|
||||
self.fetch_func = fetch_func
|
||||
|
||||
self.current_match_set_idx = 0
|
||||
@ -230,7 +230,7 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
|
||||
match = self.current_match()
|
||||
ca = ComicArchive(self.current_match_set.original_path)
|
||||
|
||||
md = ca.read_metadata(self._style)
|
||||
md = ca.read_metadata(self.config.internal__load_data_style)
|
||||
if md.is_empty:
|
||||
md = ca.metadata_from_filename(
|
||||
self.config.Filename_Parsing__complicated_parser,
|
||||
@ -247,10 +247,10 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
|
||||
|
||||
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
|
||||
md.overlay(ct_md)
|
||||
success = ca.write_metadata(md, self._style)
|
||||
for style in self._styles:
|
||||
success = ca.write_metadata(md, style)
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
if not success:
|
||||
QtWidgets.QMessageBox.warning(self, "Write Error", "Saving the tags to the archive seemed to fail!")
|
||||
|
||||
ca.load_cache(list(metadata_styles))
|
||||
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
|
||||
if not success:
|
||||
QtWidgets.QMessageBox.warning(self, "Write Error", "Saving the tags to the archive seemed to fail!")
|
||||
|
@ -23,7 +23,7 @@ def general(parser: settngs.Manager) -> None:
|
||||
def internal(parser: settngs.Manager) -> None:
|
||||
# automatic settings
|
||||
parser.add_setting("install_id", default=uuid.uuid4().hex, cmdline=False)
|
||||
parser.add_setting("save_data_style", default="cbi", cmdline=False)
|
||||
parser.add_setting("save_data_style", default=["cbi"], cmdline=False)
|
||||
parser.add_setting("load_data_style", default="cbi", cmdline=False)
|
||||
parser.add_setting("last_opened_folder", default="", cmdline=False)
|
||||
parser.add_setting("window_width", default=0, cmdline=False)
|
||||
@ -255,6 +255,11 @@ def parse_filter(config: settngs.Config[ct_ns]) -> settngs.Config[ct_ns]:
|
||||
|
||||
def validate_file_settings(config: settngs.Config[ct_ns]) -> settngs.Config[ct_ns]:
|
||||
config = parse_filter(config)
|
||||
|
||||
# TODO Remove this conversion check at a later date
|
||||
if isinstance(config[0].internal__save_data_style, str):
|
||||
config[0].internal__save_data_style = [config[0].internal__save_data_style]
|
||||
|
||||
if config[0].Filename_Parsing__protofolius_issue_number_scheme:
|
||||
config[0].Filename_Parsing__allow_issue_start_with_letter = True
|
||||
|
||||
|
@ -37,7 +37,7 @@ class settngs_namespace(settngs.TypedNS):
|
||||
Runtime_Options__files: list[str]
|
||||
|
||||
internal__install_id: str
|
||||
internal__save_data_style: str
|
||||
internal__save_data_style: list[str]
|
||||
internal__load_data_style: str
|
||||
internal__last_opened_folder: str
|
||||
internal__window_width: int
|
||||
|
@ -115,7 +115,7 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
|
||||
self.comic_archive: ComicArchive | None = None
|
||||
self.pages_list: list[ImageMetadata] = []
|
||||
self.data_style = ""
|
||||
self.data_styles: list[str] = []
|
||||
|
||||
def reset_page(self) -> None:
|
||||
self.pageWidget.clear()
|
||||
@ -326,7 +326,7 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
self.comic_archive = comic_archive
|
||||
self.pages_list = pages_list
|
||||
if pages_list:
|
||||
self.set_metadata_style(self.data_style)
|
||||
self.set_metadata_style(self.data_styles)
|
||||
else:
|
||||
self.cbPageType.setEnabled(False)
|
||||
self.chkDoublePage.setEnabled(False)
|
||||
@ -370,11 +370,16 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
self.first_front_page = self.get_first_front_cover()
|
||||
self.firstFrontCoverChanged.emit(self.first_front_page)
|
||||
|
||||
def set_metadata_style(self, data_style: str) -> None:
|
||||
def set_metadata_style(self, data_styles: list[str]) -> None:
|
||||
# depending on the current data style, certain fields are disabled
|
||||
if data_style:
|
||||
self.metadata_style = data_style
|
||||
if data_styles:
|
||||
styles = [metadata_styles[style] for style in data_styles]
|
||||
enabled_widgets = []
|
||||
for style in styles:
|
||||
for attr in style.supported_attributes:
|
||||
enabled_widgets.append(attr)
|
||||
|
||||
self.data_styles = data_styles
|
||||
|
||||
enabled_widgets = metadata_styles[data_style].supported_attributes
|
||||
for metadata, widget in self.md_attributes.items():
|
||||
enable_widget(widget, metadata in enabled_widgets)
|
||||
|
@ -61,6 +61,7 @@ from comictaggerlib.resulttypes import Action, IssueResult, MatchStatus, OnlineM
|
||||
from comictaggerlib.seriesselectionwindow import SeriesSelectionWindow
|
||||
from comictaggerlib.settingswindow import SettingsWindow
|
||||
from comictaggerlib.ui import ui_path
|
||||
from comictaggerlib.ui.customwidgets import CheckableComboBox
|
||||
from comictaggerlib.ui.qtutils import center_window_on_parent, enable_widget, reduce_widget_font_size
|
||||
from comictaggerlib.versionchecker import VersionChecker
|
||||
from comictalker.comictalker import ComicTalker, TalkerError
|
||||
@ -215,15 +216,29 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
if config[0].Runtime_Options__type and isinstance(config[0].Runtime_Options__type[0], str):
|
||||
# respect the command line option tag type
|
||||
config[0].internal__save_data_style = config[0].Runtime_Options__type[0]
|
||||
config[0].internal__save_data_style = config[0].Runtime_Options__type
|
||||
config[0].internal__load_data_style = config[0].Runtime_Options__type[0]
|
||||
|
||||
if config[0].internal__save_data_style not in metadata_styles:
|
||||
config[0].internal__save_data_style = list(metadata_styles.keys())[0]
|
||||
for style in config[0].internal__save_data_style:
|
||||
if style not in metadata_styles:
|
||||
config[0].internal__save_data_style.remove(style)
|
||||
if config[0].internal__load_data_style not in metadata_styles:
|
||||
config[0].internal__load_data_style = list(metadata_styles.keys())[0]
|
||||
self.save_data_style = config[0].internal__save_data_style
|
||||
self.load_data_style = config[0].internal__load_data_style
|
||||
self.save_data_styles: list[str] = config[0].internal__save_data_style
|
||||
self.load_data_style: str = config[0].internal__load_data_style
|
||||
|
||||
# Add multiselect combobox
|
||||
self.cbSaveDataStyle = CheckableComboBox()
|
||||
self.cbSaveDataStyle.setToolTip("At least 1 save style is required")
|
||||
# Add normal combobox for read style (TODO support multiple read styles)
|
||||
self.cbLoadDataStyle = QtWidgets.QComboBox()
|
||||
|
||||
# Need to set minimum or source_style_formLayout will resize larger than 230px which will affect the
|
||||
# file info box and cover image width underneath
|
||||
self.cbLoadDataStyle.setMinimumWidth(100)
|
||||
self.cbSaveDataStyle.setMinimumWidth(100)
|
||||
self.source_style_formLayout.addRow("Read Style", self.cbLoadDataStyle)
|
||||
self.source_style_formLayout.addRow("Modify Styles", self.cbSaveDataStyle)
|
||||
|
||||
self.setAcceptDrops(True)
|
||||
self.view_tag_actions, self.remove_tag_actions = self.tag_actions()
|
||||
@ -273,7 +288,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
# hook up the callbacks
|
||||
self.cbLoadDataStyle.currentIndexChanged.connect(self.set_load_data_style)
|
||||
self.cbSaveDataStyle.currentIndexChanged.connect(self.set_save_data_style)
|
||||
self.cbSaveDataStyle.itemChecked.connect(self.set_save_data_style)
|
||||
self.cbx_sources.currentIndexChanged.connect(self.set_source)
|
||||
self.btnEditCredit.clicked.connect(self.edit_credit)
|
||||
self.btnAddCredit.clicked.connect(self.add_credit)
|
||||
@ -438,7 +453,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.actionCopyTags.triggered.connect(self.copy_tags)
|
||||
|
||||
self.actionRemoveAuto.setShortcut("Ctrl+D")
|
||||
self.actionRemoveAuto.setStatusTip("Remove currently selected modify tag style from the archive")
|
||||
self.actionRemoveAuto.setStatusTip("Remove currently selected modify tag style(s) from the archive")
|
||||
self.actionRemoveAuto.triggered.connect(self.remove_auto)
|
||||
|
||||
self.actionRepackage.setShortcut("Ctrl+E")
|
||||
@ -1146,7 +1161,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
"Save Tags",
|
||||
f"Are you sure you wish to save {metadata_styles[self.save_data_style].name()} tags to this archive?",
|
||||
f"Are you sure you wish to save {', '.join([metadata_styles[style].name() for style in self.save_data_styles])} tags to this archive?",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes,
|
||||
QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
@ -1155,12 +1170,22 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
|
||||
self.form_to_metadata()
|
||||
|
||||
success = self.comic_archive.write_metadata(self.metadata, self.save_data_style)
|
||||
fail_list = []
|
||||
# Save each style
|
||||
for style in self.save_data_styles:
|
||||
success = self.comic_archive.write_metadata(self.metadata, style)
|
||||
if not success:
|
||||
fail_list.append(style)
|
||||
|
||||
self.comic_archive.load_cache(list(metadata_styles))
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
|
||||
if not success:
|
||||
QtWidgets.QMessageBox.warning(self, "Save failed", "The tag save operation seemed to fail!")
|
||||
if fail_list:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"Save failed",
|
||||
f"The tag save operation seemed to fail for:{', '.join([metadata_styles[style].name() for style in fail_list])}",
|
||||
)
|
||||
else:
|
||||
self.clear_dirty_flag()
|
||||
self.update_info_box()
|
||||
@ -1186,9 +1211,9 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.adjust_load_style_combo()
|
||||
self.cbLoadDataStyle.currentIndexChanged.connect(self.set_load_data_style)
|
||||
|
||||
def set_save_data_style(self, s: str) -> None:
|
||||
self.save_data_style = self.cbSaveDataStyle.itemData(s)
|
||||
self.config[0].internal__save_data_style = self.save_data_style
|
||||
def set_save_data_style(self) -> None:
|
||||
self.save_data_styles = self.cbSaveDataStyle.currentData()
|
||||
self.config[0].internal__save_data_style = self.save_data_styles
|
||||
self.update_metadata_style_tweaks()
|
||||
self.update_menus()
|
||||
|
||||
@ -1196,13 +1221,18 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.config[0].Sources__source = self.cbx_sources.itemData(s)
|
||||
|
||||
def update_metadata_credit_colors(self) -> None:
|
||||
style = metadata_styles[self.save_data_style]
|
||||
enabled = style.supported_attributes
|
||||
styles = [metadata_styles[style] for style in self.save_data_styles]
|
||||
enabled = []
|
||||
|
||||
for style in styles:
|
||||
for attr in style.supported_attributes:
|
||||
enabled.append(attr)
|
||||
|
||||
credit_attributes = [x for x in self.md_attributes.items() if "credits." in x[0]]
|
||||
|
||||
for r in range(self.twCredits.rowCount()):
|
||||
w = self.twCredits.item(r, 1)
|
||||
supports_role = style.supports_credit_role(str(w.text()))
|
||||
supports_role = any(style.supports_credit_role(str(w.text())) for style in styles)
|
||||
for credit in credit_attributes:
|
||||
widget_enabled = credit[0] in enabled
|
||||
widget = self.twCredits.item(r, credit[1])
|
||||
@ -1212,14 +1242,17 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def update_metadata_style_tweaks(self) -> None:
|
||||
# depending on the current data style, certain fields are disabled
|
||||
enabled_widgets = []
|
||||
for style in self.save_data_styles:
|
||||
for attr in metadata_styles[style].supported_attributes:
|
||||
enabled_widgets.append(attr)
|
||||
|
||||
enabled_widgets = metadata_styles[self.save_data_style].supported_attributes
|
||||
for metadata, widget in self.md_attributes.items():
|
||||
if widget is not None and not isinstance(widget, (int)):
|
||||
enable_widget(widget, metadata in enabled_widgets)
|
||||
|
||||
self.update_metadata_credit_colors()
|
||||
self.page_list_editor.set_metadata_style(self.save_data_style)
|
||||
self.page_list_editor.set_metadata_style(self.save_data_styles)
|
||||
|
||||
def cell_double_clicked(self, r: int, c: int) -> None:
|
||||
self.edit_credit()
|
||||
@ -1353,7 +1386,11 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def adjust_save_style_combo(self) -> None:
|
||||
# select the current style
|
||||
self.cbSaveDataStyle.setCurrentIndex(self.cbSaveDataStyle.findData(self.save_data_style))
|
||||
unchecked = set(metadata_styles.keys()) - set(self.save_data_styles)
|
||||
for style in self.save_data_styles:
|
||||
self.cbSaveDataStyle.setItemChecked(self.cbLoadDataStyle.findData(style), True)
|
||||
for style in unchecked:
|
||||
self.cbSaveDataStyle.setItemChecked(self.cbLoadDataStyle.findData(style), False)
|
||||
self.update_metadata_style_tweaks()
|
||||
|
||||
def populate_combo_boxes(self) -> None:
|
||||
@ -1461,19 +1498,26 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.cbFormat.addItem("Year One")
|
||||
|
||||
def remove_auto(self) -> None:
|
||||
self.remove_tags(self.save_data_style)
|
||||
self.remove_tags(self.save_data_styles)
|
||||
|
||||
def remove_tags(self, style: str) -> None:
|
||||
def remove_tags(self, styles: list[str]) -> None:
|
||||
# remove the indicated tags from the archive
|
||||
ca_list = self.fileSelectionList.get_selected_archive_list()
|
||||
has_md_count = 0
|
||||
file_md_count = {}
|
||||
for style in styles:
|
||||
file_md_count[style] = 0
|
||||
for ca in ca_list:
|
||||
if ca.has_metadata(style):
|
||||
has_md_count += 1
|
||||
for style in styles:
|
||||
if ca.has_metadata(style):
|
||||
has_md_count += 1
|
||||
file_md_count[style] += 1
|
||||
|
||||
if has_md_count == 0:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, "Remove Tags", f"No archives with {metadata_styles[style].name()} tags selected!"
|
||||
self,
|
||||
"Remove Tags",
|
||||
f"No archives with {', '.join([metadata_styles[style].name() for style in styles])} tags selected!",
|
||||
)
|
||||
return
|
||||
|
||||
@ -1486,7 +1530,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
"Remove Tags",
|
||||
f"Are you sure you wish to remove the {metadata_styles[style].name()} tags from {has_md_count} archive(s)?",
|
||||
f"Are you sure you wish to remove {', '.join([f'{metadata_styles[style].name()} tags from {count} files' for style, count in file_md_count.items()])} removing a total of {has_md_count} tag(s)?",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes,
|
||||
QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
@ -1508,12 +1552,15 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
progdialog.setValue(prog_idx)
|
||||
progdialog.setLabelText(str(ca.path))
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
if ca.has_metadata(style) and ca.is_writable():
|
||||
if not ca.remove_metadata(style):
|
||||
failed_list.append(ca.path)
|
||||
else:
|
||||
success_count += 1
|
||||
ca.load_cache(list(metadata_styles))
|
||||
for style in styles:
|
||||
if ca.has_metadata(style) and ca.is_writable():
|
||||
if not ca.remove_metadata(style):
|
||||
failed_list.append(ca.path)
|
||||
# Abandon any further tag removals to prevent any greater damage to archive
|
||||
break
|
||||
else:
|
||||
success_count += 1
|
||||
ca.load_cache(list(metadata_styles))
|
||||
|
||||
progdialog.hide()
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
@ -1521,7 +1568,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.update_info_box()
|
||||
self.update_menus()
|
||||
|
||||
summary = f"Successfully removed tags in {success_count} archive(s)."
|
||||
summary = f"Successfully removed {success_count} tags in archive(s)."
|
||||
if len(failed_list) > 0:
|
||||
summary += f"\n\nThe remove operation failed in the following {len(failed_list)} archive(s):\n"
|
||||
for f in failed_list:
|
||||
@ -1538,9 +1585,13 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
has_src_count = 0
|
||||
|
||||
src_style = self.load_data_style
|
||||
dest_style = self.save_data_style
|
||||
dest_styles = list(self.save_data_styles)
|
||||
|
||||
if src_style == dest_style:
|
||||
# Remove the read style from the write style
|
||||
if src_style in dest_styles:
|
||||
dest_styles.remove(src_style)
|
||||
|
||||
if not dest_styles:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, "Copy Tags", "Can't copy tag style onto itself. Read style and modify style must be different."
|
||||
)
|
||||
@ -1551,7 +1602,9 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
has_src_count += 1
|
||||
|
||||
if has_src_count == 0:
|
||||
QtWidgets.QMessageBox.information(self, "Copy Tags", f"No archives with {src_style} tags selected!")
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, "Copy Tags", f"No archives with {metadata_styles[src_style].name()} tags selected!"
|
||||
)
|
||||
return
|
||||
|
||||
if has_src_count != 0 and not self.dirty_flag_verification(
|
||||
@ -1563,8 +1616,9 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
"Copy Tags",
|
||||
f"Are you sure you wish to copy the {metadata_styles[src_style].name()}"
|
||||
+ f" tags to {metadata_styles[dest_style].name()} tags in {has_src_count} archive(s)?",
|
||||
f"Are you sure you wish to copy the {metadata_styles[src_style].name()} "
|
||||
f"tags to {', '.join([metadata_styles[style].name() for style in dest_styles])} tags in "
|
||||
f"{has_src_count} archive(s)?",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes,
|
||||
QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
@ -1580,26 +1634,32 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
failed_list = []
|
||||
success_count = 0
|
||||
for prog_idx, ca in enumerate(ca_list, 1):
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
if prog_dialog.wasCanceled():
|
||||
break
|
||||
ca_saved = False
|
||||
for style in dest_styles:
|
||||
if ca.has_metadata(style):
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
if prog_dialog.wasCanceled():
|
||||
break
|
||||
|
||||
prog_dialog.setValue(prog_idx)
|
||||
prog_dialog.setLabelText(str(ca.path))
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
prog_dialog.setValue(prog_idx)
|
||||
prog_dialog.setLabelText(str(ca.path))
|
||||
center_window_on_parent(prog_dialog)
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
|
||||
if ca.has_metadata(src_style) and ca.is_writable():
|
||||
md = ca.read_metadata(src_style)
|
||||
if ca.has_metadata(src_style) and ca.is_writable():
|
||||
md = ca.read_metadata(src_style)
|
||||
|
||||
if dest_style == "cbi" and self.config[0].Comic_Book_Lover__apply_transform_on_bulk_operation:
|
||||
if style == "cbi" and self.config[0].Comic_Book_Lover__apply_transform_on_bulk_operation:
|
||||
md = CBLTransformer(md, self.config[0]).apply()
|
||||
|
||||
if not ca.write_metadata(md, dest_style):
|
||||
failed_list.append(ca.path)
|
||||
else:
|
||||
success_count += 1
|
||||
if not ca.write_metadata(md, style):
|
||||
failed_list.append(ca.path)
|
||||
else:
|
||||
if not ca_saved:
|
||||
success_count += 1
|
||||
ca_saved = True
|
||||
|
||||
ca.load_cache([self.load_data_style, self.save_data_style, src_style, dest_style])
|
||||
ca.load_cache([self.load_data_style, self.save_data_style, src_style, style])
|
||||
|
||||
prog_dialog.hide()
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
@ -1652,7 +1712,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
# read in metadata, and parse file name if not there
|
||||
try:
|
||||
md = ca.read_metadata(self.save_data_style)
|
||||
md = ca.read_metadata(self.load_data_style)
|
||||
except Exception as e:
|
||||
md = GenericMetadata()
|
||||
logger.error("Failed to load metadata for %s: %s", ca.path, e)
|
||||
@ -1792,36 +1852,42 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
if self.config[0].Issue_Identifier__auto_imprint:
|
||||
md.fix_publisher()
|
||||
|
||||
if not ca.write_metadata(md, self.save_data_style):
|
||||
match_results.write_failures.append(
|
||||
Result(
|
||||
Action.save,
|
||||
Status.write_failure,
|
||||
ca.path,
|
||||
online_results=matches,
|
||||
match_status=MatchStatus.good_match,
|
||||
# Save each style
|
||||
for style in self.save_data_styles:
|
||||
if not ca.write_metadata(md, style):
|
||||
match_results.write_failures.append(
|
||||
Result(
|
||||
Action.save,
|
||||
Status.write_failure,
|
||||
ca.path,
|
||||
online_results=matches,
|
||||
match_status=MatchStatus.good_match,
|
||||
)
|
||||
)
|
||||
)
|
||||
self.auto_tag_log("Save failed ;-(\n")
|
||||
else:
|
||||
match_results.good_matches.append(
|
||||
Result(
|
||||
Action.save,
|
||||
Status.success,
|
||||
ca.path,
|
||||
online_results=matches,
|
||||
match_status=MatchStatus.good_match,
|
||||
self.auto_tag_log(
|
||||
f"{metadata_styles[style].name()} save failed! Aborting any additional style saves.\n"
|
||||
)
|
||||
)
|
||||
success = True
|
||||
self.auto_tag_log("Save complete!\n")
|
||||
ca.load_cache([self.load_data_style, self.save_data_style])
|
||||
break
|
||||
else:
|
||||
match_results.good_matches.append(
|
||||
Result(
|
||||
Action.save,
|
||||
Status.success,
|
||||
ca.path,
|
||||
online_results=matches,
|
||||
match_status=MatchStatus.good_match,
|
||||
)
|
||||
)
|
||||
success = True
|
||||
self.auto_tag_log(f"{metadata_styles[style].name()} save complete!\n")
|
||||
|
||||
ca.load_cache([self.load_data_style] + self.save_data_styles)
|
||||
|
||||
return success, match_results
|
||||
|
||||
def auto_tag(self) -> None:
|
||||
ca_list = self.fileSelectionList.get_selected_archive_list()
|
||||
style = self.save_data_style
|
||||
styles = self.save_data_styles
|
||||
|
||||
if len(ca_list) == 0:
|
||||
QtWidgets.QMessageBox.information(self, "Auto-Tag", "No archives selected!")
|
||||
@ -1837,7 +1903,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.config[0],
|
||||
(
|
||||
f"You have selected {len(ca_list)} archive(s) to automatically identify and write "
|
||||
+ metadata_styles[style].name()
|
||||
+ ", ".join([metadata_styles[style].name() for style in styles])
|
||||
+ " tags to.\n\nPlease choose config below, and select OK to Auto-Tag."
|
||||
),
|
||||
)
|
||||
@ -1864,7 +1930,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.auto_tag_log(f"Auto-Tagging {prog_idx} of {len(ca_list)}\n")
|
||||
self.auto_tag_log(f"{ca.path}\n")
|
||||
try:
|
||||
cover_idx = ca.read_metadata(style).get_cover_page_index_list()[0]
|
||||
cover_idx = ca.read_metadata(self.load_data_style).get_cover_page_index_list()[0]
|
||||
except Exception as e:
|
||||
cover_idx = 0
|
||||
logger.error("Failed to load metadata for %s: %s", ca.path, e)
|
||||
@ -1898,7 +1964,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.atprogdialog = None
|
||||
|
||||
summary = ""
|
||||
summary += f"Successfully tagged archives: {len(match_results.good_matches)}\n"
|
||||
summary += f"Successfully added {len(match_results.good_matches)} tags to archive(s)\n"
|
||||
|
||||
if len(match_results.multiple_matches) > 0:
|
||||
summary += f"Archives with multiple matches: {len(match_results.multiple_matches)}\n"
|
||||
@ -1934,7 +2000,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
matchdlg = AutoTagMatchWindow(
|
||||
self,
|
||||
match_results.multiple_matches,
|
||||
style,
|
||||
styles,
|
||||
self.actual_issue_data_fetch,
|
||||
self.config[0],
|
||||
self.current_talker(),
|
||||
|
113
comictaggerlib/ui/customwidgets.py
Normal file
113
comictaggerlib/ui/customwidgets.py
Normal file
@ -0,0 +1,113 @@
|
||||
"""Custom widgets"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from PyQt5 import QtGui, QtWidgets
|
||||
from PyQt5.QtCore import QEvent, QRect, Qt, pyqtSignal
|
||||
|
||||
|
||||
# Multiselect combobox from: https://gis.stackexchange.com/a/351152 (with custom changes)
|
||||
class CheckableComboBox(QtWidgets.QComboBox):
|
||||
itemChecked = pyqtSignal(str, bool)
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Prevent popup from closing when clicking on an item
|
||||
self.view().viewport().installEventFilter(self)
|
||||
|
||||
# Keeps track of when the combobox list is shown
|
||||
self.justShown = False
|
||||
|
||||
def resizeEvent(self, event: Any) -> None:
|
||||
# Recompute text to elide as needed
|
||||
super().resizeEvent(event)
|
||||
self._updateText()
|
||||
|
||||
def eventFilter(self, obj: Any, event: Any) -> bool:
|
||||
# Allow events before the combobox list is shown
|
||||
if obj == self.view().viewport():
|
||||
# We record that the combobox list has been shown
|
||||
if event.type() == QEvent.Show:
|
||||
self.justShown = True
|
||||
# We record that the combobox list has hidden,
|
||||
# this will happen if the user does not make a selection
|
||||
# but clicks outside of the combobox list or presses escape
|
||||
if event.type() == QEvent.Hide:
|
||||
self._updateText()
|
||||
self.justShown = False
|
||||
# QEvent.MouseButtonPress is inconsistent on activation because double clicks are a thing
|
||||
if event.type() == QEvent.MouseButtonRelease:
|
||||
# If self.justShown is true it means that they clicked on the combobox to change the checked items
|
||||
# This is standard behavior (on macos) but I think it is surprising when it has a multiple select
|
||||
if self.justShown:
|
||||
self.justShown = False
|
||||
return True
|
||||
|
||||
# Find the current index and item
|
||||
index = self.view().indexAt(event.pos())
|
||||
self.toggleItem(index.row())
|
||||
return True
|
||||
return False
|
||||
|
||||
def currentData(self) -> list[Any]:
|
||||
# Return the list of all checked items data
|
||||
res = []
|
||||
for i in range(self.count()):
|
||||
item = self.model().item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
res.append(self.itemData(i))
|
||||
return res
|
||||
|
||||
def addItem(self, text: str, data: Any = None) -> None:
|
||||
super().addItem(text, data)
|
||||
# Need to enable the checkboxes and require one checked item
|
||||
# Expected that state of *all* checkboxes will be set ('adjust_save_style_combo' in taggerwindow.py)
|
||||
if self.count() == 1:
|
||||
self.model().item(0).setCheckState(Qt.CheckState.Checked)
|
||||
|
||||
def _updateText(self) -> None:
|
||||
texts = []
|
||||
for i in range(self.count()):
|
||||
item = self.model().item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
texts.append(item.text())
|
||||
text = ", ".join(texts)
|
||||
|
||||
# Compute elided text (with "...")
|
||||
|
||||
# The QStyleOptionComboBox is needed for the call to subControlRect
|
||||
so = QtWidgets.QStyleOptionComboBox()
|
||||
# init with the current widget
|
||||
so.initFrom(self)
|
||||
|
||||
# Ask the style for the size of the text field
|
||||
rect = self.style().subControlRect(QtWidgets.QStyle.CC_ComboBox, so, QtWidgets.QStyle.SC_ComboBoxEditField)
|
||||
|
||||
# Compute the elided text
|
||||
elidedText = self.fontMetrics().elidedText(text, Qt.ElideRight, rect.width())
|
||||
|
||||
# This CheckableComboBox does not use the index, so we clear it and set the placeholder text
|
||||
self.setCurrentIndex(-1)
|
||||
self.setPlaceholderText(elidedText)
|
||||
|
||||
def setItemChecked(self, index: Any, state: bool) -> None:
|
||||
qt_state = Qt.Checked if state else Qt.Unchecked
|
||||
item = self.model().item(index)
|
||||
current = self.currentData()
|
||||
# If we have at least one item checked emit itemChecked with the current check state and update text
|
||||
# Require at least one item to be checked and provide a tooltip
|
||||
if len(current) == 1 and not state and item.checkState() == Qt.Checked:
|
||||
QtWidgets.QToolTip.showText(QtGui.QCursor.pos(), self.toolTip(), self, QRect(), 3000)
|
||||
return
|
||||
|
||||
if len(current) > 0:
|
||||
item.setCheckState(qt_state)
|
||||
self.itemChecked.emit(self.itemData(index), state)
|
||||
self._updateText()
|
||||
|
||||
def toggleItem(self, index: int) -> None:
|
||||
if self.model().item(index).checkState() == Qt.Checked:
|
||||
self.setItemChecked(index, False)
|
||||
else:
|
||||
self.setItemChecked(index, True)
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1096</width>
|
||||
<height>621</height>
|
||||
<height>658</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -52,37 +52,23 @@
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<layout class="QFormLayout" name="source_style_formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="formAlignment">
|
||||
<set>Qt::AlignHCenter|Qt::AlignTop</set>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Read Style</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="cbLoadDataStyle"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="saveStyleLabel">
|
||||
<property name="text">
|
||||
<string>Modify Style</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="cbSaveDataStyle"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="lbl_md_source">
|
||||
<property name="text">
|
||||
<string>Metadata Source</string>
|
||||
<string>Data Source</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -825,9 +811,6 @@
|
||||
<property name="text">
|
||||
<string>Primary</string>
|
||||
</property>
|
||||
<property name="textAlignment">
|
||||
<set>AlignCenter</set>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
@ -1166,7 +1149,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1096</width>
|
||||
<height>21</height>
|
||||
<height>28</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuComicTagger">
|
||||
@ -1383,7 +1366,7 @@
|
||||
</action>
|
||||
<action name="actionRemoveAuto">
|
||||
<property name="text">
|
||||
<string>Remove Current 'Modify' Tag Style</string>
|
||||
<string>Remove Current 'Modify' Tag Style(s)</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRename">
|
||||
|
Loading…
Reference in New Issue
Block a user