diff --git a/comictaggerlib/autotagmatchwindow.py b/comictaggerlib/autotagmatchwindow.py index fe62cbe..449aeec 100644 --- a/comictaggerlib/autotagmatchwindow.py +++ b/comictaggerlib/autotagmatchwindow.py @@ -230,7 +230,15 @@ class AutoTagMatchWindow(QtWidgets.QDialog): match = self.current_match() ca = ComicArchive(self.current_match_set.original_path) - md = ca.read_metadata(self.config.internal__load_data_style) + # TODO should this follow the same as CLI: filename (-f), read styles (-t), command line (-m) + # TODO Same is used for taggerwindow.py:~1734 Make a method? + md = GenericMetadata() + try: + for style in self.load_data_styles.keys(): + md.overlay(ca.read_metadata(style)) + except Exception as e: + logger.error("Failed to load metadata for %s: %s", ca.path, e) + if md.is_empty: md = ca.metadata_from_filename( self.config.Filename_Parsing__filename_parser, @@ -265,4 +273,4 @@ class AutoTagMatchWindow(QtWidgets.QDialog): ) break - ca.load_cache(list(metadata_styles)) + ca.load_cache(list(metadata_styles)) # TODO Should this be what the others do in taggerwindow.py etc.? diff --git a/comictaggerlib/cli.py b/comictaggerlib/cli.py index fe385a6..f6e7f37 100644 --- a/comictaggerlib/cli.py +++ b/comictaggerlib/cli.py @@ -249,12 +249,11 @@ class CLI: md.overlay(f_md) - for style in self.config.Runtime_Options__type: + for style in reversed(self.config.Runtime_Options__type): if ca.has_metadata(style): try: t_md = ca.read_metadata(style) md.overlay(t_md) - break except Exception as e: logger.error("Failed to load metadata for %s: %s", ca.path, e) diff --git a/comictaggerlib/ctsettings/commandline.py b/comictaggerlib/ctsettings/commandline.py index 3920f3c..b56ee9c 100644 --- a/comictaggerlib/ctsettings/commandline.py +++ b/comictaggerlib/ctsettings/commandline.py @@ -160,14 +160,14 @@ def register_runtime(parser: settngs.Manager) -> None: parser.add_setting( "--json", "-j", action="store_true", help="Output json on stdout. Ignored in interactive mode.", file=False ) - + # TODO Break read and write apart? parser.add_setting( "-t", "--type", metavar=f"{{{','.join(metadata_styles).upper()}}}", default=[], type=metadata_type, - help="""Specify TYPE as either CR, CBL or COMET\n(as either ComicRack, ComicBookLover,\nor CoMet style tags, respectively).\nUse commas for multiple types.\nFor searching the metadata will use the first listed:\neg '-t cbl,cr' with no CBL tags, CR will be used if they exist\n\n""", + help="""Specify TYPE as either CR, CBL or COMET\n(as either ComicRack, ComicBookLover,\nor CoMet style tags, respectively).\nUse commas for multiple types.\nFor searching the metadata will be overlayed in reverse order (hierarchical):\ne.g. '-t cbl,cr' first CR tags are read, then CBL overlayed over the top.\n\n""", file=False, ) parser.add_setting( diff --git a/comictaggerlib/ctsettings/file.py b/comictaggerlib/ctsettings/file.py index d1bb276..b671001 100644 --- a/comictaggerlib/ctsettings/file.py +++ b/comictaggerlib/ctsettings/file.py @@ -32,7 +32,7 @@ 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("load_data_style", default="cbi", cmdline=False) + parser.add_setting("load_data_style", default=[{"cbi": 0}], cmdline=False) parser.add_setting("last_opened_folder", default="", cmdline=False) parser.add_setting("window_width", default=0, cmdline=False) parser.add_setting("window_height", default=0, cmdline=False) diff --git a/comictaggerlib/ctsettings/settngs_namespace.py b/comictaggerlib/ctsettings/settngs_namespace.py index 5f1aaf8..b5022e0 100644 --- a/comictaggerlib/ctsettings/settngs_namespace.py +++ b/comictaggerlib/ctsettings/settngs_namespace.py @@ -41,7 +41,7 @@ class SettngsNS(settngs.TypedNS): internal__install_id: str internal__save_data_style: list[str] - internal__load_data_style: str + internal__load_data_style: dict[str, int] internal__last_opened_folder: str internal__window_width: int internal__window_height: int diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py index dc2d2bc..7c8d136 100644 --- a/comictaggerlib/settingswindow.py +++ b/comictaggerlib/settingswindow.py @@ -523,6 +523,7 @@ class SettingsWindow(QtWidgets.QDialog): 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_load_style_combo() self.parent().adjust_save_style_combo() self.config[0].Issue_Identifier__series_match_identify_thresh = self.sbNameMatchIdentifyThresh.value() diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py index 8e37a93..8ceffe8 100644 --- a/comictaggerlib/taggerwindow.py +++ b/comictaggerlib/taggerwindow.py @@ -214,15 +214,20 @@ 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 - config[0].internal__load_data_style = config[0].Runtime_Options__type[0] + # TODO Should the command line have seperate options for read and write? + cmd_read_style = {} + for i, style in enumerate(config[0].Runtime_Options__type): + cmd_read_style.update({style: i}) + config[0].internal__load_data_style = cmd_read_style 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] + for style in config[0].internal__load_data_style.keys(): + if style not in metadata_styles: + del config[0].internal__load_data_style[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.load_data_styles: dict[str, int] = config[0].internal__load_data_style self.setAcceptDrops(True) self.view_tag_actions, self.remove_tag_actions = self.tag_actions() @@ -271,7 +276,7 @@ class TaggerWindow(QtWidgets.QMainWindow): self.cbMaturityRating.lineEdit().setAcceptDrops(False) # hook up the callbacks - self.cbLoadDataStyle.itemChecked.connect(self.set_load_data_style) + self.cbLoadDataStyle.itemChanged.connect(self.set_load_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) @@ -433,7 +438,7 @@ class TaggerWindow(QtWidgets.QMainWindow): self.actionAutoTag.triggered.connect(self.auto_tag) self.actionCopyTags.setShortcut("Ctrl+C") - self.actionCopyTags.setStatusTip("Copy one tag style to another") + self.actionCopyTags.setStatusTip("Copy first read style tags to enabled modify style(s)") self.actionCopyTags.triggered.connect(self.copy_tags) self.actionRemoveAuto.setShortcut("Ctrl+D") @@ -1203,24 +1208,25 @@ class TaggerWindow(QtWidgets.QMainWindow): self.update_menus() self.fileSelectionList.update_current_row() - self.metadata = self.comic_archive.read_metadata(self.load_data_style) + self.metadata = GenericMetadata() + for style in reversed(self.load_data_styles.keys()): + self.metadata.overlay(self.comic_archive.read_metadata(style)) self.update_ui_for_archive() else: QtWidgets.QMessageBox.information(self, "Whoops!", "No data to commit!") - def set_load_data_style(self, s: str) -> None: + def set_load_data_style(self) -> None: + # TODO Should the message be changed? "manually entered data will be lost" Better desc for all 3 options? + # Save, Discard and Cancel - Cancel will keep the data in the form AND change the style. Explain each one? if self.dirty_flag_verification( - "Change Tag Read Style", "If you change read tag style now, data in the form will be lost. Are you sure?" + "Change Tag Read Style", + "If you change read tag style(s) now, data in the form will be lost. Are you sure?", ): - self.load_data_style = self.cbLoadDataStyle.itemData(s) - self.config[0].internal__load_data_style = self.load_data_style + self.load_data_styles = self.cbLoadDataStyle.currentData() + self.config[0].internal__load_data_style = self.load_data_styles self.update_menus() if self.comic_archive is not None: self.load_archive(self.comic_archive) - else: - self.cbLoadDataStyle.currentIndexChanged.disconnect(self.set_load_data_style) - self.adjust_load_style_combo() - self.cbLoadDataStyle.currentIndexChanged.connect(self.set_load_data_style) def set_save_data_style(self) -> None: self.save_data_styles = self.cbSaveDataStyle.currentData() @@ -1392,28 +1398,36 @@ class TaggerWindow(QtWidgets.QMainWindow): self.cbx_sources.setCurrentIndex(self.cbx_sources.findData(self.config[0].Sources__source)) def adjust_load_style_combo(self) -> None: - # select the current style - self.cbLoadDataStyle.setCurrentIndex(self.cbLoadDataStyle.findData(self.load_data_style)) + # select the enabled styles + unchecked = set(metadata_styles.keys()) - set(self.load_data_styles.keys()) + for style, order in self.load_data_styles.items(): + self.cbLoadDataStyle.setItemChecked(self.cbLoadDataStyle.findData(style), True, order) + for style in unchecked: + self.cbLoadDataStyle.setItemChecked(self.cbLoadDataStyle.findData(style), False) def adjust_save_style_combo(self) -> None: # select the current 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) + self.cbSaveDataStyle.setItemChecked( + self.cbSaveDataStyle.findData(style), True + ) # Why were these read style? for style in unchecked: - self.cbSaveDataStyle.setItemChecked(self.cbLoadDataStyle.findData(style), False) + self.cbSaveDataStyle.setItemChecked(self.cbSaveDataStyle.findData(style), False) self.update_metadata_style_tweaks() def populate_style_names(self) -> None: # First clear all entries (called from settingswindow.py) self.cbSaveDataStyle.clear() + self.cbLoadDataStyle.emptyTable() # Add the entries to the tag style combobox for style in metadata_styles.values(): - self.cbLoadDataStyle.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) + self.cbLoadDataStyle.addItem(style.short_name.upper(), {style.short_name: -1}) else: self.cbSaveDataStyle.addItem(style.name(), style.short_name) + self.cbLoadDataStyle.addItem(style.name(), {style.short_name: -1}) def populate_combo_boxes(self) -> None: self.populate_style_names() @@ -1604,7 +1618,8 @@ class TaggerWindow(QtWidgets.QMainWindow): ca_list = self.fileSelectionList.get_selected_archive_list() has_src_count = 0 - src_style = self.load_data_style + # TODO: Take first read style for now (make a better system later) + src_style, _ = next(iter(self.load_data_styles.items())) dest_styles = self.save_data_styles # Remove the read style from the write style @@ -1683,7 +1698,8 @@ class TaggerWindow(QtWidgets.QMainWindow): failed_list.append(ca.path) ca.reset_cache() - ca.load_cache([self.load_data_style, *self.save_data_styles]) + # TODO Could result in dupes? Should only be read styles? + ca.load_cache([*self.load_data_styles.keys(), *self.save_data_styles]) prog_dialog.hide() QtCore.QCoreApplication.processEvents() @@ -1727,10 +1743,12 @@ class TaggerWindow(QtWidgets.QMainWindow): ii = IssueIdentifier(ca, self.config[0], self.current_talker()) # read in metadata, and parse file name if not there + # TODO should this follow the same as CLI: filename (-f), read styles (-t), command line (-m) + md = GenericMetadata() try: - md = ca.read_metadata(self.load_data_style) + for style in self.load_data_styles.keys(): + md.overlay(ca.read_metadata(style)) except Exception as e: - md = GenericMetadata() logger.error("Failed to load metadata for %s: %s", ca.path, e) if md.is_empty: md = ca.metadata_from_filename( @@ -1888,7 +1906,8 @@ class TaggerWindow(QtWidgets.QMainWindow): match_results.write_failures.append(res) ca.reset_cache() - ca.load_cache([self.load_data_style] + self.save_data_styles) + # TODO Only read styles required? + ca.load_cache([*self.load_data_styles.keys(), *self.save_data_styles]) return success, match_results @@ -1937,7 +1956,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(self.load_data_style).get_cover_page_index_list()[0] + cover_idx = ca.read_metadata(list(self.load_data_styles.keys())[0]).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) @@ -2132,7 +2151,8 @@ class TaggerWindow(QtWidgets.QMainWindow): if self.dirty_flag_verification( "File Rename", "If you rename files now, unsaved data in the form will be lost. Are you sure?" ): - dlg = RenameWindow(self, ca_list, self.load_data_style, self.config, self.talkers) + # dlg = RenameWindow(self, ca_list, self.load_data_styles, self.config, self.talkers) + dlg = RenameWindow(self, ca_list, "cr", self.config, self.talkers) dlg.setModal(True) if dlg.exec() and self.comic_archive is not None: self.fileSelectionList.update_selected_rows() @@ -2151,12 +2171,15 @@ class TaggerWindow(QtWidgets.QMainWindow): self.config[0].internal__last_opened_folder = os.path.abspath(os.path.split(comic_archive.path)[0]) self.comic_archive = comic_archive + + self.metadata = GenericMetadata() try: - self.metadata = self.comic_archive.read_metadata(self.load_data_style) + for style, order in reversed(self.load_data_styles.items()): + metadata = self.comic_archive.read_metadata(style) + self.metadata.overlay(metadata) except Exception as e: logger.error("Failed to load metadata for %s: %s", self.comic_archive.path, e) self.exception(f"Failed to load metadata for {self.comic_archive.path}:\n\n{e}") - self.metadata = GenericMetadata() self.update_ui_for_archive() diff --git a/comictaggerlib/ui/customwidgets.py b/comictaggerlib/ui/customwidgets.py index 03d1984..0ad99c1 100644 --- a/comictaggerlib/ui/customwidgets.py +++ b/comictaggerlib/ui/customwidgets.py @@ -7,6 +7,8 @@ from typing import Any from PyQt5 import QtGui, QtWidgets from PyQt5.QtCore import QEvent, QModelIndex, QRect, Qt, pyqtSignal +from comictaggerlib.graphics import graphics_path + # Multiselect combobox from: https://gis.stackexchange.com/a/351152 (with custom changes) class CheckableComboBox(QtWidgets.QComboBox): @@ -114,6 +116,16 @@ class CheckableComboBox(QtWidgets.QComboBox): self.setItemChecked(index, True) +class CheckBoxStyle(QtWidgets.QProxyStyle): + def subElementRect( + self, element: QtWidgets.QStyle.SubElement, option: QtWidgets.QStyleOption, widget: QtWidgets.QWidget = None + ) -> QRect: + r = super().subElementRect(element, option, widget) + if element == QtWidgets.QStyle.SE_ItemViewItemCheckIndicator: + r.moveCenter(option.rect.center()) + return r + + class SortLabelTableWidgetItem(QtWidgets.QTableWidgetItem): """Custom QTableWidgetItem to sort with '-' below numbers""" @@ -126,30 +138,30 @@ class SortLabelTableWidgetItem(QtWidgets.QTableWidgetItem): class HoverQLabel(QtWidgets.QLabel): + """A QLabel with two QButtons that appear on hover""" + def __init__(self, text: str, parent: TableComboBox): super().__init__(text, parent=parent) self.combobox = parent - self.button_up = QtWidgets.QPushButton("Up", self) + self.button_up = QtWidgets.QPushButton(QtGui.QIcon(str(graphics_path / "up.png")), "", self) self.button_up.clicked.connect(self.button_up_clicked) + self.button_up.setToolTip("Move style up in order") self.button_up.hide() - self.button_down = QtWidgets.QPushButton("Down", self) + self.button_down = QtWidgets.QPushButton(QtGui.QIcon(str(graphics_path / "down.png")), "", self) self.button_down.clicked.connect(self.button_down_clicked) + self.button_down.setToolTip("Move style down in order") self.button_down.hide() # Place 'down' button on left side - self.button_down.move(self.width() - self.button_down.width(), 0) self.button_down.resize(self.button_down.sizeHint()) # Place 'up' button on right side - self.button_up.move(self.width() - self.button_up.width() - self.button_down.width(), 0) self.button_up.resize(self.button_up.sizeHint()) - # self.resizeEvent = self.adjustButton - def _showHideButtons(self, index: QModelIndex) -> None: - # TODO Better to iterate over all? + # TODO Better to iterate over all? Send in check state too? item = self.combobox.tableWidget.item(index.row(), 1) item_checked = item.checkState() if index.row() != self.combobox.tableWidget.currentRow(): @@ -164,6 +176,7 @@ class HoverQLabel(QtWidgets.QLabel): self.button_down.hide() def enterEvent(self, event: QEvent | None) -> None: + # Need to manually set the rest of the row highlighted index: QModelIndex = self.combobox.tableWidget.indexAt(self.pos()) self.combobox.tableWidget.selectRow(index.row()) @@ -193,22 +206,35 @@ class HoverQLabel(QtWidgets.QLabel): class TableComboBox(QtWidgets.QComboBox): - itemChecked = pyqtSignal(str, bool) + itemChanged = pyqtSignal() def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) + + # Longest width of read style label + self.longest = 0 + self.tableWidget = QtWidgets.QTableWidget() self.setModel(self.tableWidget.model()) self.setView(self.tableWidget) + centered_checkbox_style = CheckBoxStyle() + self.tableWidget.setStyle(centered_checkbox_style) + self.tableWidget.setColumnCount(3) + self.tableWidget.setHorizontalHeaderLabels([" # ", " Enabled ", "Read Style"]) + self.tableWidget.horizontalHeaderItem(0).setToolTip("Order of overlay operations") + self.tableWidget.horizontalHeaderItem(1).setToolTip("Whether the style is enabled or not") + self.tableWidget.horizontalHeaderItem(2).setToolTip("Name of the read style") + + self.tableWidget.horizontalHeader().setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) + self.tableWidget.resizeColumnsToContents() + self.tableWidget.verticalHeader().setVisible(False) - self.tableWidget.setHorizontalHeaderLabels(["Order", "Enabled", "Read Style"]) + self.tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.tableWidget.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) self.tableWidget.setShowGrid(False) - self.tableWidget.horizontalHeader().setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) - self.tableWidget.resizeColumnsToContents() # Prevent popup from closing when clicking on an item self.tableWidget.viewport().installEventFilter(self) @@ -219,6 +245,9 @@ class TableComboBox(QtWidgets.QComboBox): self.tableWidget.currentCellChanged.connect(self.current_cell_changed) def current_cell_changed(self, cur_row: int, cur_col: int, prev_row: int, prev_col: int) -> None: + # When rebuilding, cur_row -1 will occur and cause a crash + if cur_row == -1: + return if prev_row == -1: # First time open cur_index = self.tableWidget.indexFromItem(self.tableWidget.item(cur_row, 0)) @@ -231,6 +260,42 @@ class TableComboBox(QtWidgets.QComboBox): cur_index = self.tableWidget.indexFromItem(self.tableWidget.item(cur_row, 0)) self.tableWidget.cellWidget(cur_row, 2)._showHideButtons(cur_index) + def _longest_label(self) -> None: + # Depending on "short" names for metadata, "Read Style" header or metadata name may be longer + style_header_width = 0 + header_item = self.tableWidget.horizontalHeaderItem(2) + if header_item is not None: + font_metrics = QtGui.QFontMetrics(header_item.font()) + style_header_width = font_metrics.width(header_item.text()) + + header_width = 0 + for col in range(self.tableWidget.columnCount() - 1): # Skip "read style" as already done above + header_item = self.tableWidget.horizontalHeaderItem(col) + if header_item is not None: + font_metrics = QtGui.QFontMetrics(header_item.font()) + text_width = font_metrics.width(header_item.text()) + header_width += text_width + + # Now check items + for i in range(self.count()): + hlabel = self.tableWidget.cellWidget(i, 2) + hlabel_width = ( + style_header_width if style_header_width > hlabel.sizeHint().width() else hlabel.sizeHint().width() + ) + # Get sizeHint of one button and double it + total_width = hlabel_width + header_width + (hlabel.button_up.sizeHint().width() * 2) + + if total_width > self.longest: + self.longest = total_width + + def _resizeTable(self) -> None: + self._longest_label() + self.tableWidget.setMinimumWidth(self.longest) + + def resizeEvent(self, event: Any | None = None) -> None: + super().resizeEvent(event) + self._updateText() + def eventFilter(self, obj: Any, event: Any) -> bool: # Allow events before the combobox list is shown if obj == self.tableWidget.viewport(): @@ -258,7 +323,12 @@ class TableComboBox(QtWidgets.QComboBox): return True return False + def emptyTable(self) -> None: + self.tableWidget.setRowCount(0) + self.longest = 0 + def _move_item(self, index: QModelIndex, up: bool) -> None: + """Move an item up or down in order""" adjust = -1 if up else 1 cur_item = self.tableWidget.item(index.row(), 0) cur_item_data = cur_item.data(Qt.UserRole) @@ -274,26 +344,40 @@ class TableComboBox(QtWidgets.QComboBox): swap_item.setData(Qt.UserRole, {swap_key: cur_value}) self._updateLabels() + self.itemChanged.emit() # Selected (highlighted) row moves so is no longer under the mouse self.tableWidget.selectRow(index.row()) - def addItem(self, label: str = "-", checked: bool = False, text: str = "", data: Any | None = None) -> None: + def addItem(self, text: str = "", data: Any | None = None) -> None: rowPosition = self.tableWidget.rowCount() self.tableWidget.insertRow(rowPosition) - self.tableWidget.setItem(rowPosition, 0, SortLabelTableWidgetItem(label)) + sortTblItem = SortLabelTableWidgetItem() + sortTblItem.setTextAlignment(Qt.AlignCenter) + self.tableWidget.setItem(rowPosition, 0, sortTblItem) chkBoxItem = QtWidgets.QTableWidgetItem() - chkBoxItem.setCheckState(Qt.Checked if checked else Qt.Unchecked) + # Set to true to get around the "one item must be checked" check for setItemChecked + chkBoxItem.setCheckState(Qt.Checked) + self.tableWidget.setItem(rowPosition, 1, chkBoxItem) self.tableWidget.setCellWidget(rowPosition, 2, HoverQLabel(text, parent=self)) self.tableWidget.item(rowPosition, 0).setData(Qt.UserRole, data) self._updateLabels() self._updateText() + # Manual as resizeEvent doesn't trigger + self._resizeTable() + + def findData(self, data: str, role: int = Qt.UserRole) -> QModelIndex | None: + for i in range(self.count()): + item = self.itemData(i) + k = list(item.keys())[0] + if k == data: + return self.tableWidget.indexFromItem(self.tableWidget.item(i, 0)) + return None def currentData(self) -> dict[str, int]: - # Return the list of all checked items data res = {} for i in range(self.count()): item = self.tableWidget.item(i, 1) @@ -332,7 +416,7 @@ class TableComboBox(QtWidgets.QComboBox): # Disable top up button and bottom down button if val == 1: self.tableWidget.cellWidget(i, 2).button_up.setEnabled(False) - # Disable the down button if single item. Show buttons as a sign it is checked + # Disable the down button if single item. Show buttons even if disabled to indicate checked if val == cur_data_len: self.tableWidget.cellWidget(i, 2).button_down.setEnabled(False) elif val == cur_data_len: @@ -369,33 +453,35 @@ class TableComboBox(QtWidgets.QComboBox): self.setCurrentIndex(-1) self.setPlaceholderText(elidedText) - def setItemChecked(self, index: QModelIndex, state: bool) -> None: + def setItemChecked(self, index: QModelIndex, state: bool, order: int = -1) -> None: + if index is None: + return qt_state = Qt.Checked if state else Qt.Unchecked item = self.tableWidget.item(index.row(), 1) - current = self.currentData() - # If we have at least one item checked emit itemChecked with the current check state and update text + current_len = len(self.currentData()) + # Require at least one item to be checked and provide a tooltip - if len(current) == 1 and not state and item.checkState() == Qt.Checked: + if current_len == 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: + if current_len > 0: item.setCheckState(qt_state) item_data: dict[str, int] = self.itemData(index.row()) key_name = list(item_data.keys())[0] if state: - next_num = self._nextOrderNumber() - data = {key_name: next_num} - self.tableWidget.item(index.row(), 0).setText(str(next_num + 1)) + order_num = order if order != -1 else self._nextOrderNumber() + data = {key_name: order_num} + self.tableWidget.item(index.row(), 0).setText(str(order_num + 1)) self.tableWidget.item(index.row(), 0).setData(Qt.UserRole, data) else: data = {key_name: -1} self.tableWidget.item(index.row(), 0).setText("-") self.tableWidget.item(index.row(), 0).setData(Qt.UserRole, data) - # We need to check the order numbers as any number could have been removed + # Any number may have been removed so reevaluate all self._setOrderNumbers() - self.itemChecked.emit(key_name, state) + self.itemChanged.emit() self._updateText() self._updateLabels() # Check if buttons need to be shown or hidden diff --git a/comictaggerlib/ui/taggerwindow.ui b/comictaggerlib/ui/taggerwindow.ui index 2601888..d071c0a 100644 --- a/comictaggerlib/ui/taggerwindow.ui +++ b/comictaggerlib/ui/taggerwindow.ui @@ -76,7 +76,11 @@ - + + + At least one read style must be selected + +