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

View File

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