diff --git a/comictaggerlib/autotagmatchwindow.py b/comictaggerlib/autotagmatchwindow.py
index a9494d7..3d3ba78 100644
--- a/comictaggerlib/autotagmatchwindow.py
+++ b/comictaggerlib/autotagmatchwindow.py
@@ -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,15 @@ 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",
+ f"Saving {metadata_styles[style].name()} the tags to the archive seemed to fail!",
+ )
+ break
+
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!")
diff --git a/comictaggerlib/ctsettings/file.py b/comictaggerlib/ctsettings/file.py
index 21488e5..fb555c5 100644
--- a/comictaggerlib/ctsettings/file.py
+++ b/comictaggerlib/ctsettings/file.py
@@ -18,12 +18,13 @@ def general(parser: settngs.Manager) -> None:
action=argparse.BooleanOptionalAction,
help="Disable the ComicRack metadata type",
)
+ parser.add_setting("use_short_metadata_names", default=False, action=argparse.BooleanOptionalAction, cmdline=False)
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 +256,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
diff --git a/comictaggerlib/ctsettings/settngs_namespace.py b/comictaggerlib/ctsettings/settngs_namespace.py
index b807eaf..896c334 100644
--- a/comictaggerlib/ctsettings/settngs_namespace.py
+++ b/comictaggerlib/ctsettings/settngs_namespace.py
@@ -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
@@ -97,6 +97,7 @@ class settngs_namespace(settngs.TypedNS):
General__check_for_new_version: bool
General__disable_cr: bool
+ General__use_short_metadata_names: bool
Dialog_Flags__show_disclaimer: bool
Dialog_Flags__dont_notify_about_this_version: str
diff --git a/comictaggerlib/pagelisteditor.py b/comictaggerlib/pagelisteditor.py
index 1c5fd20..9511203 100644
--- a/comictaggerlib/pagelisteditor.py
+++ b/comictaggerlib/pagelisteditor.py
@@ -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,15 @@ 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 = set()
+ for style in styles:
+ enabled_widgets.update(style.supported_attributes)
+
+ 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)
diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py
index bc5f5c2..23c3358 100644
--- a/comictaggerlib/settingswindow.py
+++ b/comictaggerlib/settingswindow.py
@@ -376,6 +376,7 @@ class SettingsWindow(QtWidgets.QDialog):
self.tePublisherFilter.setPlainText("\n".join(self.config[0].Issue_Identifier__publisher_filter))
self.cbxCheckForNewVersion.setChecked(self.config[0].General__check_for_new_version)
+ self.cbxShortMetadataNames.setChecked(self.config[0].General__use_short_metadata_names)
self.cbxComplicatedParser.setChecked(self.config[0].Filename_Parsing__complicated_parser)
self.cbxRemoveC2C.setChecked(self.config[0].Filename_Parsing__remove_c2c)
@@ -493,6 +494,12 @@ class SettingsWindow(QtWidgets.QDialog):
self.config[0].General__check_for_new_version = self.cbxCheckForNewVersion.isChecked()
+ # Update metadata style names if required
+ if self.cbxShortMetadataNames.isChecked() != self.config[0].General__use_short_metadata_names:
+ self.config[0].General__use_short_metadata_names = self.cbxShortMetadataNames.isChecked()
+ self.parent().populate_style_names()
+ self.parent().adjust_save_style_combo()
+
self.config[0].Issue_Identifier__series_match_identify_thresh = self.sbNameMatchIdentifyThresh.value()
self.config[0].Issue_Identifier__series_match_search_thresh = self.sbNameMatchSearchThresh.value()
self.config[0].Issue_Identifier__publisher_filter = utils.split(self.tePublisherFilter.toPlainText(), "\n")
diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py
index a695f4d..9e31465 100644
--- a/comictaggerlib/taggerwindow.py
+++ b/comictaggerlib/taggerwindow.py
@@ -215,15 +215,16 @@ 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
self.setAcceptDrops(True)
self.view_tag_actions, self.remove_tag_actions = self.tag_actions()
@@ -273,7 +274,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 +439,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 +1147,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 +1156,23 @@ 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)
+ failed_style: str = ""
+ # Save each style
+ for style in self.save_data_styles:
+ success = self.comic_archive.write_metadata(self.metadata, style)
+ if not success:
+ failed_style = metadata_styles[style].name()
+ break
+
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 failed_style:
+ QtWidgets.QMessageBox.warning(
+ self,
+ "Save failed",
+ f"The tag save operation seemed to fail for: {failed_style}",
+ )
else:
self.clear_dirty_flag()
self.update_info_box()
@@ -1186,9 +1198,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 +1208,16 @@ 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 = set()
+ for style in styles:
+ enabled.update(style.supported_attributes)
+
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 +1227,16 @@ class TaggerWindow(QtWidgets.QMainWindow):
def update_metadata_style_tweaks(self) -> None:
# depending on the current data style, certain fields are disabled
+ enabled_widgets = set()
+ for style in self.save_data_styles:
+ enabled_widgets.update(metadata_styles[style].supported_attributes)
- 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,14 +1370,26 @@ 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:
+ def populate_style_names(self) -> None:
+ # First clear all entries (called from settingswindow.py)
+ self.cbSaveDataStyle.clear()
# Add the entries to the tag style combobox
for style in metadata_styles.values():
self.cbLoadDataStyle.addItem(style.name(), style.short_name)
- self.cbSaveDataStyle.addItem(style.name(), style.short_name)
+ if self.config[0].General__use_short_metadata_names:
+ self.cbSaveDataStyle.addItem(style.short_name.upper(), style.short_name)
+ else:
+ self.cbSaveDataStyle.addItem(style.name(), style.short_name)
+
+ def populate_combo_boxes(self) -> None:
+ self.populate_style_names()
self.adjust_load_style_combo()
self.adjust_save_style_combo()
@@ -1461,19 +1490,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 +1522,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 +1544,16 @@ 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 ca.remove_metadata(style):
+ success_count += 1
+ else:
+ failed_list.append(ca.path)
+ # Abandon any further tag removals to prevent any greater damage to archive
+ break
+ ca.reset_cache()
+ ca.load_cache(list(metadata_styles))
progdialog.hide()
QtCore.QCoreApplication.processEvents()
@@ -1521,7 +1561,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 +1578,13 @@ class TaggerWindow(QtWidgets.QMainWindow):
has_src_count = 0
src_style = self.load_data_style
- dest_style = self.save_data_style
+ dest_styles = 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 +1595,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 +1609,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 +1627,36 @@ 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
-
- prog_dialog.setValue(prog_idx)
- prog_dialog.setLabelText(str(ca.path))
- QtCore.QCoreApplication.processEvents()
+ ca_saved = False
if ca.has_metadata(src_style) and ca.is_writable():
md = ca.read_metadata(src_style)
+ else:
+ continue
- if dest_style == "cbi" and self.config[0].Comic_Book_Lover__apply_transform_on_bulk_operation:
+ 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))
+ center_window_on_parent(prog_dialog)
+ QtCore.QCoreApplication.processEvents()
+
+ 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)
+ if ca.write_metadata(md, style):
+ if not ca_saved:
+ success_count += 1
+ ca_saved = True
else:
- success_count += 1
+ failed_list.append(ca.path)
- ca.load_cache([self.load_data_style, self.save_data_style, src_style, dest_style])
+ ca.reset_cache()
+ ca.load_cache([self.load_data_style, *self.save_data_styles])
prog_dialog.hide()
QtCore.QCoreApplication.processEvents()
@@ -1647,12 +1704,22 @@ class TaggerWindow(QtWidgets.QMainWindow):
def identify_and_tag_single_archive(
self, ca: ComicArchive, match_results: OnlineMatchResults, dlg: AutoTagStartWindow
) -> tuple[bool, OnlineMatchResults]:
+ def metadata_save() -> bool:
+ for style in self.save_data_styles:
+ # write out the new data
+ if not ca.write_metadata(md, style):
+ self.auto_tag_log(
+ f"{metadata_styles[style].name()} save failed! Aborting any additional style saves.\n"
+ )
+ return False
+ return True
+
success = False
ii = IssueIdentifier(ca, self.config[0], self.current_talker())
# 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 +1859,33 @@ 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,
- )
- )
- 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,
- )
- )
+ res = Result(
+ Action.save,
+ status=Status.success,
+ original_path=ca.path,
+ online_results=matches,
+ match_status=MatchStatus.good_match,
+ md=md,
+ tags_written=self.save_data_styles,
+ )
+
+ # Save styles
+ if metadata_save():
+ match_results.good_matches.append(res)
success = True
self.auto_tag_log("Save complete!\n")
- ca.load_cache([self.load_data_style, self.save_data_style])
+ else:
+ res.status = Status.write_failure
+ match_results.write_failures.append(res)
+
+ ca.reset_cache()
+ 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 +1901,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 +1928,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 +1962,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.atprogdialog = None
summary = ""
- summary += f"Successfully tagged archives: {len(match_results.good_matches)}\n"
+ summary += f"Successfully added {', '.join([metadata_styles[style].name() for style in self.save_data_styles])} tags to {len(match_results.good_matches)} archive(s)\n"
if len(match_results.multiple_matches) > 0:
summary += f"Archives with multiple matches: {len(match_results.multiple_matches)}\n"
@@ -1934,7 +1998,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(),
diff --git a/comictaggerlib/ui/customwidgets.py b/comictaggerlib/ui/customwidgets.py
new file mode 100644
index 0000000..b4efabf
--- /dev/null
+++ b/comictaggerlib/ui/customwidgets.py
@@ -0,0 +1,114 @@
+"""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)
diff --git a/comictaggerlib/ui/settingswindow.ui b/comictaggerlib/ui/settingswindow.ui
index 33e1663..9e571b9 100644
--- a/comictaggerlib/ui/settingswindow.ui
+++ b/comictaggerlib/ui/settingswindow.ui
@@ -6,8 +6,8 @@
0
0
- 702
- 559
+ 703
+ 574
@@ -41,7 +41,7 @@
- -
+
-
@@ -54,23 +54,7 @@
- -
-
-
-
- 0
- 0
-
-
-
- Revert to default settings
-
-
- true
-
-
-
- -
+
-
@@ -83,7 +67,7 @@
- -
+
-
@@ -99,6 +83,22 @@
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Revert to default settings
+
+
+ true
+
+
+
-
@@ -106,6 +106,16 @@
+ -
+
+
+ Use the short name for the metadata styles (CBI, CR, etc.)
+
+
+ Use "short" names for metadata styles
+
+
+
@@ -411,7 +421,7 @@
11
21
251
- 199
+ 206
diff --git a/comictaggerlib/ui/taggerwindow.ui b/comictaggerlib/ui/taggerwindow.ui
index 0033a28..4d253a3 100644
--- a/comictaggerlib/ui/taggerwindow.ui
+++ b/comictaggerlib/ui/taggerwindow.ui
@@ -7,7 +7,7 @@
0
0
1096
- 621
+ 658
@@ -52,43 +52,49 @@
-
-
-
+
QFormLayout::AllNonFixedFieldsGrow
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
Qt::AlignHCenter|Qt::AlignTop
-
-
-
-
- Read Style
-
-
-
- -
-
-
- -
-
-
- Modify Style
-
-
-
- -
-
-
-
- Metadata Source
+ Data Source
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
-
+ -
+
+
+ -
+
+
+ -
+
+
+ Read Style
+
+
+
+ -
+
+
+ Modify Styles
+
+
+
-
@@ -825,9 +831,6 @@
Primary
-
- AlignCenter
-
@@ -1166,7 +1169,7 @@
0
0
1096
- 21
+ 28
+
+
+ CheckableComboBox
+ QComboBox
+ comictaggerlib.ui.customwidgets
+
+