First pass at hierarchical tags

This commit is contained in:
Charles Haley 2011-02-22 13:19:35 +00:00
parent 12b8a5e12a
commit 40af13276e
2 changed files with 80 additions and 42 deletions

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
Browsing book collection by tags. Browsing book collection by tags.
''' '''
import traceback import traceback, copy
from itertools import izip from itertools import izip
from functools import partial from functools import partial
@ -551,11 +551,12 @@ class TagTreeItem(object): # {{{
def child_tags(self): def child_tags(self):
res = [] res = []
for t in self.children: def recurse(nodes, res):
if t.type == TagTreeItem.CATEGORY: for t in nodes:
res.extend(t.child_tags()) if t.type != TagTreeItem.CATEGORY:
else: res.append(t)
res.append(t) recurse(t.children, res)
recurse(self.children, res)
return res return res
# }}} # }}}
@ -603,6 +604,7 @@ class TagsModel(QAbstractItemModel): # {{{
tt = '' tt = ''
else: else:
tt = _(u'The lookup/search name is "{0}"').format(r) tt = _(u'The lookup/search name is "{0}"').format(r)
if r.startswith('@') and r.find('/') >= 0: if r.startswith('@') and r.find('/') >= 0:
path_parts = [p.strip() for p in r.split('/') if p.strip()] path_parts = [p.strip() for p in r.split('/') if p.strip()]
path = '' path = ''
@ -858,7 +860,7 @@ class TagsModel(QAbstractItemModel): # {{{
for idx,tag in enumerate(data[key]): for idx,tag in enumerate(data[key]):
if clear_rating: if clear_rating:
tag.avg_rating = None tag.avg_rating = None
tag.state = state_map.get(tag.name, 0) tag.state = state_map.get((tag.name, tag.category), 0)
if collapse_model != 'disable' and cat_len > collapse: if collapse_model != 'disable' and cat_len > collapse:
if collapse_model == 'partition': if collapse_model == 'partition':
@ -875,7 +877,6 @@ class TagsModel(QAbstractItemModel): # {{{
data = name, tooltip = None, data = name, tooltip = None,
category_icon = category_node.icon, category_icon = category_node.icon,
category_key=category_node.category_key) category_key=category_node.category_key)
sub_cat_index = self.createIndex(sub_cat.row(), 0, sub_cat)
self.endInsertRows() self.endInsertRows()
else: else:
ts = tag.sort ts = tag.sort
@ -897,22 +898,45 @@ class TagsModel(QAbstractItemModel): # {{{
category_icon = category_node.icon, category_icon = category_node.icon,
tooltip = None, tooltip = None,
category_key=category_node.category_key) category_key=category_node.category_key)
sub_cat_index = self.createIndex(sub_cat.row(), 0, sub_cat) node_parent = sub_cat
self.beginInsertRows(sub_cat_index, 999999, 1)
TagTreeItem(parent=sub_cat, data=tag, tooltip=tt,
icon_map=self.icon_state_map)
else: else:
node_parent = category
components = [t for t in tag.name.split('.')]
if key in ['authors', 'publisher', 'title'] or len(components) == 1:
self.beginInsertRows(category_index, 999999, 1) self.beginInsertRows(category_index, 999999, 1)
TagTreeItem(parent=category, data=tag, tooltip=tt, TagTreeItem(parent=node_parent, data=tag, tooltip=tt,
icon_map=self.icon_state_map) icon_map=self.icon_state_map)
self.endInsertRows() self.endInsertRows()
else:
print components
for i,comp in enumerate(components):
children = dict([(t.tag.name, t) for t in node_parent.children
if t.type != TagTreeItem.CATEGORY])
if comp in children:
node_parent = children[comp]
else:
if i < len(components)-1:
t = copy.copy(tag)
t.original_name = '.'.join(components[:i+1])
t.use_prefix = True
else:
t = tag
t.original_name = t.name
t.use_prefix = False
t.name = comp
self.beginInsertRows(category_index, 999999, 1)
node_parent = TagTreeItem(parent=node_parent, data=t,
tooltip=tt, icon_map=self.icon_state_map)
self.endInsertRows()
return ((collapse_letter, collapse_letter_sk)) return ((collapse_letter, collapse_letter_sk))
for category in self.category_nodes: for category in self.category_nodes:
if len(category.children) > 0: if len(category.children) > 0:
children = category.children children = category.children
states = [c.tag.state for c in children if c.type != TagTreeItem.CATEGORY] states = [c.tag.state for c in category.child_tags()]
names = [c.tag.name for c in children if c.type != TagTreeItem.CATEGORY] names = [(c.tag.name, c.tag.category) for c in category.child_tags()]
state_map = dict(izip(names, states)) state_map = dict(izip(names, states))
ctags = [c for c in children if c.type == TagTreeItem.CATEGORY] ctags = [c for c in children if c.type == TagTreeItem.CATEGORY]
start = len(ctags) start = len(ctags)
@ -1064,27 +1088,31 @@ class TagsModel(QAbstractItemModel): # {{{
def reset_all_states(self, except_=None): def reset_all_states(self, except_=None):
update_list = [] update_list = []
def process_tag(tag_index, tag_item): def process_tag(tag_item):
tag = tag_item.tag if tag_item.type != TagTreeItem.CATEGORY:
if tag is except_: tag = tag_item.tag
self.dataChanged.emit(tag_index, tag_index) if tag is except_:
return tag_index = self.createIndex(tag_item.row(), 0, tag_item)
if tag.state != 0 or tag in update_list: self.dataChanged.emit(tag_index, tag_index)
tag.state = 0 elif tag.state != 0 or tag in update_list:
update_list.append(tag) tag_index = self.createIndex(tag_item.row(), 0, tag_item)
self.dataChanged.emit(tag_index, tag_index) tag.state = 0
update_list.append(tag)
self.dataChanged.emit(tag_index, tag_index)
for t in tag_item.children:
process_tag(t)
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.type == TagTreeItem.CATEGORY: # if tag_item.type == TagTreeItem.CATEGORY:
process_level(tag_index) # process_level(tag_index)
else: # else:
process_tag(tag_index, tag_item) # process_tag(tag_index, tag_item)
for i in xrange(self.rowCount(QModelIndex())): for t in self.root_item.children:
process_level(self.index(i, 0, QModelIndex())) process_tag(t)
def clear_state(self): def clear_state(self):
self.reset_all_states() self.reset_all_states()
@ -1127,15 +1155,18 @@ class TagsModel(QAbstractItemModel): # {{{
if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating
ans.append('%s%s:%s'%(prefix, category, len(tag.name))) ans.append('%s%s:%s'%(prefix, category, len(tag.name)))
else: else:
name = getattr(tag, 'original_name', tag.name)
use_prefix = getattr(tag, 'use_prefix', False)
if category == 'tags': if category == 'tags':
if tag.name in tags_seen: if name in tags_seen:
continue continue
tags_seen.add(tag.name) tags_seen.add(name)
if tag in nodes_seen: if tag in nodes_seen:
continue continue
nodes_seen.add(tag) nodes_seen.add(tag)
ans.append('%s%s:"=%s"'%(prefix, category, ans.append('%s%s:"=%s%s"'%(prefix, category,
tag.name.replace(r'"', r'\"'))) '.' if use_prefix else '',
name.replace(r'"', r'\"')))
return ans return ans
def find_item_node(self, key, txt, start_path): def find_item_node(self, key, txt, start_path):

View File

@ -124,9 +124,16 @@ def _match(query, value, matchkind):
for t in value: for t in value:
t = icu_lower(t) t = icu_lower(t)
try: ### ignore regexp exceptions, required because search-ahead tries before typing is finished try: ### ignore regexp exceptions, required because search-ahead tries before typing is finished
if ((matchkind == EQUALS_MATCH and query == t) or if (matchkind == EQUALS_MATCH):
(matchkind == REGEXP_MATCH and re.search(query, t, re.I)) or ### search unanchored if query[0] == '.':
(matchkind == CONTAINS_MATCH and query in t)): if t.startswith(query[1:]):
ql = len(query) - 1
print ql, t, query
return (len(t) == ql) or (t[ql:ql+1] == '.')
elif query == t:
return True
elif ((matchkind == REGEXP_MATCH and re.search(query, t, re.I)) or ### search unanchored
(matchkind == CONTAINS_MATCH and query in t)):
return True return True
except re.error: except re.error:
pass pass