1) Performance improvements when using hierarchical categories

2) ensure correct counts when using sub-categories
3) make drag & drop copy a node and its children
4) make tb searching work better with subcategories and hierarchies
This commit is contained in:
Charles Haley 2011-02-25 09:23:30 +00:00
parent 9eda085a3b
commit 2b24a487fa
2 changed files with 76 additions and 32 deletions

View File

@ -537,6 +537,7 @@ class TagTreeItem(object): # {{{
parent=None, tooltip=None, category_key=None):
self.parent = parent
self.children = []
self.id_set = set()
self.boxed = False
if self.parent is not None:
self.parent.append(self)
@ -613,10 +614,12 @@ class TagTreeItem(object): # {{{
name = tag.name
tt_author = False
if role == Qt.DisplayRole:
if tag.count == 0:
count = len(self.id_set)
count = count if count > 0 else tag.count
if count == 0:
return QVariant('%s'%(name))
else:
return QVariant('[%d] %s'%(tag.count, name))
return QVariant('[%d] %s'%(count, name))
if role == Qt.EditRole:
return QVariant(original_name(tag))
if role == Qt.DecorationRole:
@ -751,6 +754,7 @@ class TagsModel(QAbstractItemModel): # {{{
if idx.isValid():
# get some useful serializable data
node = idx.internalPointer()
path = self.path_for_index(idx)
if node.type == TagTreeItem.CATEGORY:
d = (node.type, node.py_name, node.category_key)
else:
@ -759,7 +763,7 @@ class TagsModel(QAbstractItemModel): # {{{
while p.type != TagTreeItem.CATEGORY:
p = p.parent
d = (node.type, p.category_key, p.is_gst, original_name(t),
t.category, t.id)
t.category, path)
data.append(d)
else:
data.append(None)
@ -798,38 +802,30 @@ class TagsModel(QAbstractItemModel): # {{{
'''
src is a list of tuples representing items to copy. The tuple is
(type, containing category key, category key is global search term,
full name, category key, id)
The 'id' member is ignored, and can be None.
full name, category key, path to node)
The type must be TagTreeItem.TAG
dest is the TagTreeItem node to receive the items
action is Qt.CopyAction or Qt.MoveAction
'''
##### TODO: must handle children of item being copied
user_cats = self.db.prefs.get('user_categories', {})
parent_node = None
copied_node = None
for s in src:
src_parent, src_parent_is_gst, src_name, src_cat = s[1:5]
parent_node = src_parent
if src_parent.startswith('@'):
is_uc = True
src_parent = src_parent[1:]
else:
is_uc = False
dest_key = dest.category_key[1:]
if dest_key not in user_cats:
continue
new_cat = []
def process_source_node(user_cats, src_parent, src_parent_is_gst,
is_uc, dest_key, node):
'''
Copy/move an item and all its children to the destination
'''
copied = False
src_name = original_name(node.tag)
src_cat = node.tag.category
# delete the item if the source is a user category and action is move
if is_uc and not src_parent_is_gst and src_parent in user_cats and \
action == Qt.MoveAction:
new_cat = []
for tup in user_cats[src_parent]:
if src_name == tup[0] and src_cat == tup[1]:
continue
new_cat.append(list(tup))
user_cats[src_parent] = new_cat
else:
copied_node = (src_parent, src_name)
copied = True
# Now add the item to the destination user category
add_it = True
@ -841,18 +837,53 @@ class TagsModel(QAbstractItemModel): # {{{
if add_it:
user_cats[dest_key].append([src_name, src_cat, 0])
for c in node.children:
copied = process_source_node(user_cats, src_parent, src_parent_is_gst,
is_uc, dest_key, c)
return copied
user_cats = self.db.prefs.get('user_categories', {})
parent_node = None
copied = False
path = None
for s in src:
src_parent, src_parent_is_gst = s[1:3]
path = s[5]
parent_node = src_parent
if src_parent.startswith('@'):
is_uc = True
src_parent = src_parent[1:]
else:
is_uc = False
dest_key = dest.category_key[1:]
if dest_key not in user_cats:
continue
node = self.index_for_path(path)
if node:
copied = process_source_node(user_cats, src_parent, src_parent_is_gst,
is_uc, dest_key, node.internalPointer())
self.db.prefs.set('user_categories', user_cats)
self.tags_view.recount()
# Scroll to the item copied. If it was moved, scroll to the parent
if parent_node is not None:
self.clear_boxed()
m = self.tags_view.model()
if copied_node is not None:
path = m.find_item_node(parent_node, copied_node[1], None,
equals_match=True)
else:
if not copied:
p = path[-1]
if p == 0:
path = m.find_category_node(parent_node)
else:
path[-1] = p - 1
idx = m.index_for_path(path)
self.tags_view.setExpanded(idx, True)
if idx.internalPointer().type == TagTreeItem.TAG:
m.show_item_at_index(idx, boxed=True)
else:
m.show_item_at_index(idx)
return True
@ -1131,6 +1162,8 @@ class TagsModel(QAbstractItemModel): # {{{
self.beginInsertRows(category_index, 999999, 1)
n = TagTreeItem(parent=node_parent, data=tag, tooltip=tt,
icon_map=self.icon_state_map)
if tag.id_set is not None:
n.id_set |= tag.id_set
category_child_map[tag.name, tag.category] = n
self.endInsertRows()
tag.is_editable = key != 'formats' and (key == 'news' or \
@ -1146,8 +1179,6 @@ class TagsModel(QAbstractItemModel): # {{{
if t.type != TagTreeItem.CATEGORY])
if (comp,tag.category) in child_map:
node_parent = child_map[(comp,tag.category)]
if not in_uc:
node_parent.tag.count += tag.count
node_parent.tag.is_hierarchical = True
else:
if i < len(components)-1:
@ -1166,6 +1197,8 @@ class TagsModel(QAbstractItemModel): # {{{
tooltip=tt, icon_map=self.icon_state_map)
child_map[(comp,tag.category)] = node_parent
self.endInsertRows()
# This id_set must not be None
node_parent.id_set |= tag.id_set
return ((collapse_letter, collapse_letter_sk))
@ -1583,11 +1616,16 @@ class TagsModel(QAbstractItemModel): # {{{
if tag_item.boxed:
tag_item.boxed = False
self.dataChanged.emit(tag_index, tag_index)
for i,c in enumerate(tag_item.children):
process_tag(self.index(i, 0, tag_index), c)
def process_level(category_index):
for j in xrange(self.rowCount(category_index)):
tag_index = self.index(j, 0, category_index)
tag_item = tag_index.internalPointer()
if tag_item.boxed:
tag_item.boxed = False
self.dataChanged.emit(tag_index, tag_index)
if tag_item.type == TagTreeItem.CATEGORY:
process_level(tag_index)
else:

View File

@ -46,13 +46,14 @@ copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
class Tag(object):
def __init__(self, name, id=None, count=0, state=0, avg=0, sort=None,
tooltip=None, icon=None, category=None):
tooltip=None, icon=None, category=None, id_set=None):
self.name = name
self.id = id
self.count = count
self.state = state
self.is_hierarchical = False
self.is_editable = True
self.id_set = id_set
self.avg_rating = avg/2.0 if avg is not None else 0
self.sort = sort
if self.avg_rating > 0:
@ -1162,6 +1163,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.n = name
self.s = sort
self.c = 0
self.id_set = set()
self.rt = 0
self.rc = 0
self.id = None
@ -1266,6 +1268,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
item = tag_class(val, sort_val)
tcategories[cat][val] = item
item.c += 1
item.id_set.add(book[0])
item.id = item_id
if rating > 0:
item.rt += rating
@ -1283,6 +1286,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
item = tag_class(val, sort_val)
tcategories[cat][val] = item
item.c += 1
item.id_set.add(book[0])
item.id = item_id
if rating > 0:
item.rt += rating
@ -1370,7 +1374,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
categories[category] = [tag_class(formatter(r.n), count=r.c, id=r.id,
avg=avgr(r), sort=r.s, icon=icon,
tooltip=tooltip, category=category)
tooltip=tooltip, category=category,
id_set=r.id_set)
for r in items]
#print 'end phase "tags list":', time.clock() - last, 'seconds'
@ -1379,6 +1384,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# Needed for legacy databases that have multiple ratings that
# map to n stars
for r in categories['rating']:
r.id_set = None
for x in categories['rating']:
if r.name == x.name and r.id != x.id:
r.count = r.count + x.count