From 2252d7f088b006e3944fb0572b34fcc8387a3b5b Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 25 Dec 2021 13:54:33 +0000 Subject: [PATCH] Bug #1955732: Hierarchical entries in user category may not merge correctly in tag browser Note that this fix works with grouped search terms. 'Real' user categories will never be merged because the source category is part of the 'name' --- src/calibre/db/categories.py | 8 +++-- src/calibre/gui2/tag_browser/model.py | 50 +++++++++++++++++---------- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/calibre/db/categories.py b/src/calibre/db/categories.py index b3ec3e1a9f..0fd61aeb56 100644 --- a/src/calibre/db/categories.py +++ b/src/calibre/db/categories.py @@ -44,7 +44,8 @@ class Tag: @property def string_representation(self): - return '%s:%s:%s:%s:%s'%(self.name, self.count, self.id, self.state, self.category) + return '%s:%s:%s:%s:%s:%s'%(self.name, self.count, self.id, self.state, + self.category, self.original_categories) def __str__(self): return self.string_representation @@ -207,10 +208,11 @@ def get_categories(dbcache, sort='name', book_ids=None, first_letter_sort=False) for c in gst: if c not in muc: continue - user_categories[c] = [] + uc = [] for sc in gst[c]: for t in categories.get(sc, ()): - user_categories[c].append([t.name, sc, 0]) + uc.append([t.name, sc, 0]) + user_categories[c] = uc if user_categories: # We want to use same node in the user category as in the source diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index 125553dcf2..4d6d1fdcf0 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -56,13 +56,14 @@ class TagTreeItem: # {{{ file_icon_provider = None def __init__(self, data=None, is_category=False, icon_map=None, - parent=None, tooltip=None, category_key=None, temporary=False): + parent=None, tooltip=None, category_key=None, temporary=False, + is_gst=False): if self.file_icon_provider is None: self.file_icon_provider = TagTreeItem.file_icon_provider = file_icon_provider().icon_from_ext self.parent = parent self.children = [] self.blank = QIcon() - self.is_gst = False + self.is_gst = is_gst self.boxed = False self.temporary = False self.can_be_edited = False @@ -103,6 +104,12 @@ class TagTreeItem: # {{{ del self.parent del self.children + def root_node(self): + p = self + while p.parent.type != self.ROOT: + p = p.parent + return p + def ensure_icon(self): if self.icon_state_map[0] is not None: return @@ -111,7 +118,10 @@ class TagTreeItem: # {{{ fmt = self.tag.original_name.replace('ORIGINAL_', '') cc = self.file_icon_provider(fmt) else: - cc = self.category_custom_icons.get(self.tag.category, None) + if self.is_gst: + cc = self.category_custom_icons.get(self.root_node().category_key, None) + else: + cc = self.category_custom_icons.get(self.tag.category, None) elif self.type == self.CATEGORY: cc = self.category_custom_icons.get(self.category_key, None) self.icon_state_map[0] = cc or QIcon() @@ -461,6 +471,7 @@ class TagsModel(QAbstractItemModel): # {{{ node = self.create_node(parent=last_category_node, data=p[1:] if i == 0 else p, is_category=True, + is_gst = is_gst, tooltip=tt if path == key else path, category_key=path, icon_map=self.icon_state_map) @@ -468,7 +479,6 @@ class TagsModel(QAbstractItemModel): # {{{ category_node_map[path] = node self.category_nodes.append(node) node.can_be_edited = (not is_gst) and (i == (len(path_parts)-1)) - node.is_gst = is_gst if not is_gst: node.tag.is_hierarchical = '5state' tree_root[p] = {} @@ -481,9 +491,9 @@ class TagsModel(QAbstractItemModel): # {{{ node = self.create_node(parent=self.root_item, data=self.categories[key], is_category=True, + is_gst = False, tooltip=tt, category_key=key, icon_map=self.icon_state_map) - node.is_gst = False category_node_map[key] = node last_category_node = node self.category_nodes.append(node) @@ -663,10 +673,10 @@ class TagsModel(QAbstractItemModel): # {{{ sub_cat = self.create_node(parent=category, data=name, tooltip=None, temporary=True, is_category=True, + is_gst=is_gst, category_key=category.category_key, icon_map=self.icon_state_map) sub_cat.tag.is_searchable = False - sub_cat.is_gst = is_gst node_parent = sub_cat last_idx = idx # remember where we last partitioned else: @@ -678,10 +688,10 @@ class TagsModel(QAbstractItemModel): # {{{ sub_cat = self.create_node(parent=category, data=collapse_letter, is_category=True, + is_gst=is_gst, tooltip=None, temporary=True, category_key=category.category_key, icon_map=self.icon_state_map) - sub_cat.is_gst = is_gst node_parent = sub_cat else: node_parent = category @@ -698,28 +708,29 @@ class TagsModel(QAbstractItemModel): # {{{ (fm['is_custom'] and fm['display'].get('is_names', False)) or not category_is_hierarchical or len(components) == 1): n = self.create_node(parent=node_parent, data=tag, tooltip=tt, - icon_map=self.icon_state_map) + is_gst=is_gst, icon_map=self.icon_state_map) category_child_map[tag.name, tag.category] = n else: + child_key = key if is_gst else tag.category for i,comp in enumerate(components): if i == 0: child_map = category_child_map top_level_component = comp else: - child_map = {(t.tag.name, t.tag.category): t - for t in node_parent.children + child_map = {(t.tag.name, key if is_gst else t.tag.category): + t for t in node_parent.children if t.type != TagTreeItem.CATEGORY} - if (comp,tag.category) in child_map: - node_parent = child_map[(comp,tag.category)] + if (comp,child_key) in child_map: + node_parent = child_map[(comp,child_key)] t = node_parent.tag t.is_hierarchical = '5state' if tag.category != 'search' else '3state' if tag.id_set is not None and t.id_set is not None: t.id_set = t.id_set | tag.id_set - intermediate_nodes[t.original_name, t.category] = t + intermediate_nodes[t.original_name,child_key] = t else: if i < len(components)-1: original_name = '.'.join(components[:i+1]) - t = intermediate_nodes.get((original_name, tag.category), None) + t = intermediate_nodes.get((original_name, child_key), None) if t is None: t = copy.copy(tag) t.original_name = original_name @@ -730,18 +741,19 @@ class TagsModel(QAbstractItemModel): # {{{ t.is_editable = False else: t.is_searchable = t.is_editable = False - intermediate_nodes[original_name, tag.category] = t + intermediate_nodes[original_name,child_key] = t else: t = tag if not in_uc: t.original_name = t.name - intermediate_nodes[t.original_name, t.category] = t + intermediate_nodes[t.original_name,child_key] = t t.is_hierarchical = \ '5state' if t.category != 'search' else '3state' t.name = comp - node_parent = self.create_node(parent=node_parent, data=t, - tooltip=tt, icon_map=self.icon_state_map) - child_map[(comp,tag.category)] = node_parent + node_parent = self.create_node(parent=node_parent, + data=t, is_gst=is_gst, tooltip=tt, + icon_map=self.icon_state_map) + child_map[(comp, child_key)] = node_parent # Correct the average rating for the node total = count = 0