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'
This commit is contained in:
Charles Haley 2021-12-25 13:54:33 +00:00
parent eb162a4821
commit 2252d7f088
2 changed files with 36 additions and 22 deletions

View File

@ -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

View File

@ -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
@ -110,6 +117,9 @@ class TagTreeItem: # {{{
if self.tag.category == 'formats':
fmt = self.tag.original_name.replace('ORIGINAL_', '')
cc = self.file_icon_provider(fmt)
else:
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:
@ -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