From 2da65972b74e9ea8a2ded0d374656ff5f65a2122 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 21 Jun 2020 14:45:36 +0100 Subject: [PATCH] Enhancement #1884402: Check Library: add delete checkboxes to deletable error categories to select all books in that category. --- src/calibre/gui2/dialogs/check_library.py | 119 +++++++++++++++++----- 1 file changed, 95 insertions(+), 24 deletions(-) diff --git a/src/calibre/gui2/dialogs/check_library.py b/src/calibre/gui2/dialogs/check_library.py index b87e8dda3c..eb1a3f9c82 100644 --- a/src/calibre/gui2/dialogs/check_library.py +++ b/src/calibre/gui2/dialogs/check_library.py @@ -80,6 +80,9 @@ class Item(QTreeWidgetItem): class CheckLibraryDialog(QDialog): + is_deletable = 1 + is_fixable = 2 + def __init__(self, parent, db): QDialog.__init__(self, parent) self.db = db @@ -251,33 +254,44 @@ class CheckLibraryDialog(QDialog): def builder(tree, checker, check): attr, h, checkable, fixable = check - list = getattr(checker, attr, None) - if list is None: + list_ = getattr(checker, attr, None) + if list_ is None: self.problem_count[attr] = 0 return else: - self.problem_count[attr] = len(list) + self.problem_count[attr] = len(list_) tl = Item() tl.setText(0, h) if fixable and list: + tl.setData(1, Qt.UserRole, self.is_fixable) tl.setText(1, _('(fixable)')) tl.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) tl.setCheckState(1, False) else: - tl.setFlags(Qt.ItemIsEnabled) + tl.setData(1, Qt.UserRole, self.is_deletable) + tl.setData(2, Qt.UserRole, self.is_deletable) + tl.setText(1, _('(deletable)')) + tl.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) + tl.setCheckState(1, False) + if attr == 'extra_covers': + tl.setData(2, Qt.UserRole, self.is_deletable) + tl.setText(2, _('(deletable)')) + tl.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) + tl.setCheckState(2, False) self.top_level_items[attr] = tl - for problem in list: + for problem in list_: it = Item() if checkable: it.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable) - it.setCheckState(1, False) + it.setCheckState(2, False) + it.setData(2, Qt.UserRole, self.is_deletable) else: it.setFlags(Qt.ItemIsEnabled) it.setText(0, problem[0]) it.setData(0, Qt.UserRole, problem[2]) - it.setText(1, problem[1]) + it.setText(2, problem[1]) tl.addChild(it) self.all_items.append(it) plaintext.append(','.join([h, problem[0], problem[1]])) @@ -285,8 +299,8 @@ class CheckLibraryDialog(QDialog): t = self.log t.clear() - t.setColumnCount(2) - t.setHeaderLabels([_('Name'), _('Path from library')]) + t.setColumnCount(3) + t.setHeaderLabels([_('Name'), '', _('Path from library')]) self.all_items = [] self.top_level_items = {} self.problem_count = {} @@ -304,26 +318,83 @@ class CheckLibraryDialog(QDialog): self.log.resizeColumnToContents(1) def item_changed(self, item, column): - self.fix_button.setEnabled(False) - for it in self.top_level_items.values(): - if it.checkState(1): - self.fix_button.setEnabled(True) + def set_delete_boxes(node, col, to_what): + self.log.blockSignals(True) + if col: + node.setCheckState(col, to_what) + for i in range(0, node.childCount()): + node.child(i).setCheckState(2, to_what) + self.log.blockSignals(False) - self.delete_button.setEnabled(False) - for it in self.all_items: - if it.checkState(1): - self.delete_button.setEnabled(True) - return + def is_child_delete_checked(node): + checked = False + all_checked = True + for i in range(0, node.childCount()): + c = node.child(i).checkState(2) + checked = checked or c == Qt.Checked + all_checked = all_checked and c == Qt.Checked + return (checked, all_checked) + + def any_child_delete_checked(): + for parent in self.top_level_items.values(): + (c, _) = is_child_delete_checked(parent) + if c: + return True + return False + + def any_fix_checked(): + for parent in self.top_level_items.values(): + if (parent.data(1, Qt.UserRole) == self.is_fixable and + parent.checkState(1) == Qt.Checked): + return True; + return False + + if item in self.top_level_items.values(): + if item.childCount() > 0: + if item.data(1, Qt.UserRole) == self.is_fixable and column == 1: + if item.data(2, Qt.UserRole) == self.is_deletable: + set_delete_boxes(item, 2, False) + else: + set_delete_boxes(item, column, item.checkState(column)) + if column == 2: + self.log.blockSignals(True) + item.setCheckState(1, False) + self.log.blockSignals(False) + else: + item.setCheckState(column, Qt.Unchecked) + else: + for parent in self.top_level_items.values(): + if parent.data(2, Qt.UserRole) == self.is_deletable: + (child_chkd, all_chkd) = is_child_delete_checked(parent) + if all_chkd and child_chkd: + check_state = Qt.Checked + elif child_chkd: + check_state = Qt.PartiallyChecked + else: + check_state = Qt.Unchecked + self.log.blockSignals(True) + if parent.data(1, Qt.UserRole) == self.is_fixable: + parent.setCheckState(2, check_state) + else: + parent.setCheckState(1, check_state) + if child_chkd and parent.data(1, Qt.UserRole) == self.is_fixable: + parent.setCheckState(1, Qt.Unchecked) + self.log.blockSignals(False) + self.delete_button.setEnabled(any_child_delete_checked()) + self.fix_button.setEnabled(any_fix_checked()) def mark_for_fix(self): for it in self.top_level_items.values(): - if it.flags() & Qt.ItemIsUserCheckable: + if (it.flags() & Qt.ItemIsUserCheckable and + it.data(1, Qt.UserRole) == self.is_fixable and + it.childCount() > 0): it.setCheckState(1, Qt.Checked) def mark_for_delete(self): for it in self.all_items: - if it.flags() & Qt.ItemIsUserCheckable: - it.setCheckState(1, Qt.Checked) + if (it.flags() & Qt.ItemIsUserCheckable and + it.data(2, Qt.UserRole) == self.is_deletable): + it.setCheckState(2, Qt.Checked) def delete_marked(self): if not confirm('

'+_('The marked files and folders will be ' @@ -336,9 +407,9 @@ class CheckLibraryDialog(QDialog): key=lambda x: len(x.text(1)), reverse=True) for it in items: - if it.checkState(1): + if it.checkState(2) == Qt.Checked: try: - p = os.path.join(self.db.library_path, unicode_type(it.text(1))) + p = os.path.join(self.db.library_path, unicode_type(it.text(2))) if os.path.isdir(p): delete_tree(p) else: @@ -346,7 +417,7 @@ class CheckLibraryDialog(QDialog): except: prints('failed to delete', os.path.join(self.db.library_path, - unicode_type(it.text(1)))) + unicode_type(it.text(2)))) self.run_the_check() def fix_missing_formats(self):